using SB.Core;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace SB
{
    using BS = BuildSystem;
    public class CompileCommandsEmitter : TaskEmitter
    {
        public CompileCommandsEmitter(IToolchain Toolchain) => this.Toolchain = Toolchain;
        public override bool EnableEmitter(Target Target) =>
            (Target.GetTargetType() == TargetType.HeaderOnly) ||
            Target.HasFilesOf<CppFileList>() || Target.HasFilesOf<CFileList>() || Target.HasFilesOf<ObjCppFileList>() || Target.HasFilesOf<ObjCFileList>();

        public override bool EmitTargetTask(Target Target) => Target.GetTargetType() == TargetType.HeaderOnly || Target.HasFilesOf<CppFileList>();
        public override IArtifact? PerTargetTask(Target Target)
        {
            if (Target.GetTargetType() != TargetType.HeaderOnly)
                return null;
            if (!Target.PublicArguments.TryGetValue("IncludeDirs", out var ArgList))
                return null;

            if (ArgList is ArgumentList<string> IncludeDirs)
            {
                // Generate a source file for compile commands
                var DstDirectory = Path.Combine(Target.GetStorePath(BS.GeneratedSourceStore), "compile_commands");
                if (!Directory.Exists(DstDirectory))
                    Directory.CreateDirectory(DstDirectory);
                string SourceFile = Path.Combine(DstDirectory, BS.GetUniqueTempFileName(Target.Name, "compile_commands", "cpp"));
                // get & include all headers in IncludeDirs
                var IncludeFiles0 = IncludeDirs.SelectMany(D => Directory.GetFiles(D, "*.h", SearchOption.AllDirectories));
                var IncludeFiles1 = IncludeDirs.SelectMany(D => Directory.GetFiles(D, "*.hpp", SearchOption.AllDirectories));
                var IncludeFiles = IncludeFiles0.Concat(IncludeFiles1).ToList();
                if (IncludeFiles.Count > 0)
                {
                    // Ensure file is created
                    if (!File.Exists(SourceFile))
                        File.Create(SourceFile).Close();
                    // Write the source file
                        using (var Writer = new StreamWriter(SourceFile))
                        {
                            Writer.WriteLine("// This file is generated by Sakura Engine Build System");
                            Writer.WriteLine("// It is used to generate compile commands for the project");
                            foreach (var IncludeFile in IncludeFiles)
                            {
                                Writer.WriteLine($"#include \"{IncludeFile}\"");
                            }
                        }
                }
                GenerateForFile(Target, CFamily.Cpp, SourceFile, null);
            }
            return null;
        }


        public override bool EmitFileTask(Target Target, FileList FileList) => FileList.Is<CppFileList>() || FileList.Is<CFileList>() || FileList.Is<ObjCppFileList>() || FileList.Is<ObjCFileList>();
        public override IArtifact? PerFileTask(Target Target, FileList FileList, FileOptions? Options, string SourceFile)
        {
            Stopwatch sw = new();
            sw.Start();

            CFamily Language = FileList.Is<ObjCppFileList>() ? CFamily.ObjCpp : FileList.Is<ObjCFileList>() ? CFamily.ObjC : FileList.Is<CppFileList>() ? CFamily.Cpp : CFamily.C;
            GenerateForFile(Target, Language, SourceFile, Options);

            sw.Stop();
            Time += (int)sw.ElapsedMilliseconds;
            return null;
        }

        public static void WriteToFile(string Path)
        {
            File.WriteAllText(Path, "[" + String.Join(",", CompileCommands) + "]");
        }

        private void GenerateForFile(Target Target, CFamily Language, string SourceFile, FileOptions? FileOptions)
        {
            var SourceDependencies = Path.Combine(Target.GetStorePath(BS.DepsStore), BS.GetUniqueTempFileName(SourceFile, Target.Name + this.Name, "source.deps.json"));
            var ObjectFile = GetObjectFilePath(Target, SourceFile);
            var CLDriver = Toolchain.Compiler.CreateArgumentDriver(Language, false)
                .AddArguments(Target.Arguments)
                .MergeArguments(FileOptions?.Arguments, true)
                .AddArgument("Source", SourceFile)
                .AddArgument("Object", ObjectFile)
                .AddArgument("SourceDependencies", SourceDependencies);

            var JSON = CLDriver.CompileCommands(Toolchain.Compiler, Target.Directory);
            CompileCommands.Add(JSON);
        }

        private static string GetObjectFilePath(Target Target, string SourceFile) => Path.Combine(Target.GetStorePath(BuildSystem.ObjsStore), BuildSystem.GetUniqueTempFileName(SourceFile, Target.Name, "obj"));

        private static ConcurrentBag<string> CompileCommands = new();
        private IToolchain Toolchain { get; }
        public static volatile int Time = 0;
    }
}
